package fr.cephb.operonj.locustree;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;

import com.sleepycat.je.DatabaseException;

import fr.cephb.operonj.locustree.LocusTreeDB.MappedObject;

public class LocusFrame extends JFrame
	{
	private static final Logger LOG= Logger.getLogger("fr.cephb.operonj.locustree");
	private static final long serialVersionUID = 1L;
	private int start=0;
	private int length=0;
	private int chromosome_length=0;
	//private int chromosome_id;
	private Point mouseStart;
	private Point mouseCurrent;
	private boolean dirty_flag=true;
	private JTextField leftTextField;
	private JTextField rightTextField;
	private LocusTreeDB locusTreeDB;
	private JPanel drawingArea;
	LocusFrame(final LocusTreeDB locusTreeDB)
		throws DatabaseException
		{
		super("LocusFrame");
		setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		this.locusTreeDB=locusTreeDB;
		this.start=0;
		this.length= locusTreeDB.getChromosomeLength("chr22");
		this.chromosome_length=(int)this.length;
		
		JPanel content= new JPanel(new BorderLayout());
		
		addWindowListener(new WindowAdapter()
			{
			@Override
			public void windowClosed(WindowEvent e)
				{
				try
					{
					getLocusTree().close();
					}
				catch (DatabaseException e1)
					{
					LOG.warning(e1.getMessage());
					}
				}
			});
		
		this.drawingArea=new JPanel(null)
			{
			private static final long serialVersionUID = 1L;

			@Override
			protected void paintComponent(Graphics g)
				{
				//super.paintComponent(g);
				paintDrawingArea(Graphics2D.class.cast(g));
				}
			};
		this.drawingArea.setOpaque(true);
		this.drawingArea.setBackground(Color.WHITE);
		
		content.add(this.drawingArea,BorderLayout.CENTER);
		this.setContentPane(content);
		
		MouseAdapter mouse= new MouseAdapter()
			{
			@Override
			public void mousePressed(MouseEvent e)
				{
				mouseStart=new Point(e.getX(),e.getY());
				mouseCurrent=null;
				}
			@Override
			public void mouseDragged(MouseEvent e)
				{
				if(mouseCurrent!=null)
					{
					Graphics2D g=(Graphics2D)drawingArea.getGraphics();
					g.setXORMode(drawingArea.getBackground());
					g.fillRect(Math.min(mouseStart.x,mouseCurrent.x), 0,Math.abs(mouseStart.x-mouseCurrent.x) , drawingArea.getHeight());
					g.fillRect(Math.min(mouseStart.x,e.getX()), 0,Math.abs(mouseStart.x-e.getX()) , drawingArea.getHeight());
					g.setPaintMode();
					g.dispose();
					}
				mouseCurrent=new Point(e.getX(),e.getY());
				}
			
			@Override
			public void mouseReleased(MouseEvent e)
				{
				if(mouseCurrent!=null && mouseCurrent.x!=mouseStart.x)
					{
					start=(int)(start+ length*(Math.min(mouseStart.x, mouseCurrent.x)/(double)drawingArea.getWidth()));
					
					length=(int)((Math.abs(mouseStart.x-mouseCurrent.x)/(double)drawingArea.getWidth())*length);
					if(length<10) length=0;
					if(length> chromosome_length)
						{
						length=chromosome_length;
						}
					if(start+length> chromosome_length)
						{
						start=chromosome_length-length;
						}
					if(start< 0) start=0;
					dirty_flag=true;
					updateTextField();
					}
				drawingArea.repaint();
				}
			};
		
		this.drawingArea.addMouseListener(mouse);
		this.drawingArea.addMouseMotionListener(mouse);
		JToolBar toolBar= new JToolBar();
		content.add(toolBar,BorderLayout.NORTH);
		toolBar.add(new JButton(new AbstractAction("Zoom In 10X")
			{
			@Override
			public void actionPerformed(ActionEvent arg0)
				{
				zoom(0.1f);
				}
			}));
		toolBar.add(new JButton(new AbstractAction("Zoom In 3X")
			{
			@Override
			public void actionPerformed(ActionEvent arg0)
				{
				zoom(0.3f);
				}
			}));
		toolBar.add(new JButton(new AbstractAction("Zoom Out 3X")
			{
			@Override
			public void actionPerformed(ActionEvent arg0)
				{
				zoom(3f);
				}
			}));
		toolBar.add(new JButton(new AbstractAction("Zoom Out 10X")
			{
			@Override
			public void actionPerformed(ActionEvent arg0)
				{
				zoom(10f);
				}
			}));
		
		toolBar.add(new JButton(new AbstractAction("<<")
			{
			@Override
			public void actionPerformed(ActionEvent arg0)
				{
				shift(-10);
				}
			}));
		toolBar.add(new JButton(new AbstractAction("<")
			{
			@Override
			public void actionPerformed(ActionEvent arg0)
				{
				shift(-3);
				}
			}));
		
		toolBar.add(new JButton(new AbstractAction("[+]")
			{
			@Override
			public void actionPerformed(ActionEvent arg0)
				{
				start=0;
				length=chromosome_length;
				dirty_flag=true;
				updateTextField();
				drawingArea.repaint();
				}
			}));
		
		toolBar.add(new JButton(new AbstractAction(">")
			{
			@Override
			public void actionPerformed(ActionEvent arg0)
				{
				shift(3);
				}
			}));
		
		toolBar.add(new JButton(new AbstractAction(">>")
			{
			@Override
			public void actionPerformed(ActionEvent arg0)
				{
				shift(10); 
				}
			}));
		toolBar.add(new JLabel("Start:",JLabel.RIGHT));
		toolBar.add(leftTextField=new JTextField("0",9));
		toolBar.add(new JLabel("End:",JLabel.RIGHT));
		toolBar.add(rightTextField=new JTextField(""+chromosome_length,9));
		}
	
	
	
	public LocusTreeDB getLocusTree()
		{
		return locusTreeDB;
		}
	
	private static final class MappedObjectSorter
		implements Comparator<MappedObject>
		{
		@Override
		public int compare(MappedObject a, MappedObject b)
			{
			int i=a.chromosome_id-b.chromosome_id;
			if(i!=0) return i;
			i=a.typeId-b.typeId;
			if(i!=0) return i;
			i=a.start-b.start;
			if(i!=0) return i;
			i=a.name.compareTo(b.name);
			return i;
			}
		}
	
	private class MyScanner
		extends Scanner
		{
		BufferedImage offscreen_=null;
		Graphics2D g;
		int y_offset=0;
		Set<MappedObject> mapped= new TreeSet<MappedObject>(new MappedObjectSorter());
		Map<Integer,List<StatPoint>> histo=new HashMap<Integer, List<StatPoint>>();
		Map<Integer,Integer> maximums =new HashMap<Integer, Integer>();
		MyScanner()
			{
			this.chromosome="chr22";
			this.start=LocusFrame.this.start;
			this.end= LocusFrame.this.start+LocusFrame.this.length;
			this.resolution= (LocusFrame.this.length/drawingArea.getWidth())*10;
			//if(this.resolution<100) this.resolution=100;
			if(this.resolution==0) this.resolution=1;
			}
		
		double length()
			{
			return end-start;
			}
		int base2pixel(int pos)
			{
			if(this.offscreen_==null) throw new IllegalArgumentException("offscreen is null");
			return  (int)(((pos-this.start)/length())*this.offscreen_.getWidth());
			}
		@Override
		public void histogram(Scanner.StatPoint pt,int typeId)
			{
			//System.err.println("histo:"+pt);
			List<StatPoint> list=histo.get(typeId);
			if(list==null)
				{
				list= new ArrayList<StatPoint>(drawingArea.getWidth());
				histo.put(typeId, list);
				}
			list.add(pt);
			//System.err.println("count ("+typeId+")="+list.size());
			Integer m= maximums.get(typeId);
			
			if(m==null) m=0;
			if(pt.count>m)
				{
				maximums.put(typeId, pt.count);
				
				}
			
			}
		
		@Override
		public void object(MappedObject object)
			{
			if(abort==true) return;
			mapped.add(object);
			}
		
		public void paint()
			{
			final int feature_height=15;
			g.setFont(new Font("Dialog",Font.PLAIN,feature_height-4));
			//System.err.println("mapped.size="+this.mapped.size());
			int prev_x=0;
			for(MappedObject object:this.mapped)
				{
				if(abort==true) return;
				int x0=base2pixel(object.start);
				int x1=base2pixel(object.end);
				
				if(prev_x>= x0)
					{
					y_offset+=(feature_height+2);
					if(y_offset> offscreen.getHeight())
						{
						abort=true;
						break;
						}
					}
				prev_x=x1;
				g.setColor(Color.RED);
				g.fillRect(x0, y_offset, x1-x0, feature_height);
				g.setColor(Color.BLACK);
				g.drawRect(x0, y_offset, x1-x0, feature_height);
				g.drawString(object.name, Math.max(x0,0), y_offset+feature_height-2);
				}
			y_offset+=(feature_height+2);
			//System.err.println("histo.size="+this.histo.size());
			for(Integer typeId: this.histo.keySet())
				{
				if(abort==true) return;
				Integer max= maximums.get(typeId);
				if(max==null) continue;
				for(Scanner.StatPoint object:this.histo.get(typeId))
					{
					if(abort==true) return;
					if(object.count==0) continue;
					
					int x0=base2pixel(object.start);
					int x1=base2pixel(object.end);
					int h= 2*(int)((object.count/(float)max)*(feature_height));
					g.setColor(Color.GREEN);
					g.fillRect(x0, y_offset+feature_height-h, x1-x0,h);
					g.setColor(Color.BLACK);
					g.drawRect(x0, y_offset+feature_height-h, x1-x0,h);
					}
				y_offset+=(2*feature_height+2);
				if(y_offset> offscreen.getHeight()) abort=true;
				}
			}
		}
	private BufferedImage offscreen=null;
	
	private void paintDrawingArea(Graphics2D g2)
		{
		if(offscreen==null ||
			offscreen.getWidth()!=drawingArea.getWidth() ||
			offscreen.getHeight()!=drawingArea.getHeight()
			)
			{
			this.offscreen= new BufferedImage(
					drawingArea.getWidth(),
					drawingArea.getHeight(),
					BufferedImage.TYPE_INT_RGB
					);
			dirty_flag=true;
			}
		if(dirty_flag)
			{
			MyScanner scanner= new MyScanner();
			scanner.offscreen_=offscreen;
			scanner.g= this.offscreen.createGraphics();
			scanner.g.setColor(Color.WHITE);
			scanner.g.fillRect(0, 0, offscreen.getWidth(),offscreen.getHeight());
			
			try
				{
				getLocusTree().find(scanner);
				scanner.paint();
				}
			catch (Exception e)
				{
				e.printStackTrace();
				}
			scanner.g.dispose();
			dirty_flag=false;
			}
		g2.setColor(Color.BLUE);
		g2.drawImage(this.offscreen,0,0,drawingArea);
		g2.drawString(
				"start: "+this.start+" length: "+this.length+"    end: "+(this.start+this.length),
				10,
				drawingArea.getHeight()-20
				);
		}
	
	private void updateTextField()
		{
		this.leftTextField.setText(String.valueOf(start));
		this.rightTextField.setText(String.valueOf(start+length));
		}
	
	private void shift(int scale)
		{
		if(scale==0 || scale==1) return;
		int dx = (int)(this.length/(double)scale);
		if(dx==0) dx=1;
		this.start+=dx;
		
		if(this.start+this.length>=this.chromosome_length)
			{
			this.start= this.chromosome_length-this.length;
			}
		if(this.start<0) this.start=0;
		dirty_flag=true;
		updateTextField();
		drawingArea.repaint();
		}
	
	
	private void zoom(float scale)
		{
		if(scale==0f || scale==1f) return;
		long length = (long)(this.length*scale);
		if(length<1) length=1;
		if(length> this.chromosome_length)
			length= this.chromosome_length;
		int dx=((int)length-this.length)/2;
		this.length=(int)length;
		this.start-=dx;
		if(this.start<0) this.start=0;
		dirty_flag=true;
		updateTextField();
		drawingArea.repaint();
		}
	

	
	/**
	 * @param args
	 */
	public static void main(String[] args)
		{
		LOG.setLevel(Level.OFF);
		JFrame.setDefaultLookAndFeelDecorated(true);
		LocusTreeDB app=new LocusTreeDB();
		
		try
			{
			app.open();
			app.load(new File("/home/operon/jeter.locusmap.xls"));
			final LocusFrame f= new LocusFrame(app);
			Dimension d= Toolkit.getDefaultToolkit().getScreenSize();
			f.setBounds(50, 50, d.width-100, d.height-100);
			SwingUtilities.invokeLater(new Runnable()
				{
				@Override
				public void run()
					{
					f.setVisible(true);
					}
				});
			}
		catch (Exception e)
			{
			e.printStackTrace();
			}

		}

	}
